<script setup>
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import * as monaco from 'monaco-editor'
import usePreferencesStore from 'stores/preferences.js'
import { useThemeVars } from 'naive-ui'
import { isEmpty } from 'lodash'

const props = defineProps({
    content: {
        type: String,
    },
    language: {
        type: String,
        default: 'json',
    },
    readonly: {
        type: String,
    },
    loading: {
        type: Boolean,
    },
    border: {
        type: Boolean,
        default: false,
    },
    resetKey: {
        type: String,
        default: '',
    },
    offsetKey: {
        type: String,
        default: '',
    },
    keepOffset: {
        type: Boolean,
        default: false,
    },
})

const emit = defineEmits(['reset', 'input', 'save'])

const themeVars = useThemeVars()
/** @type {HTMLElement|null} */
const editorRef = ref(null)
/** @type monaco.editor.IStandaloneCodeEditor */
let editorNode = null
const scrollOffset = { top: 0, left: 0 }

const updateScroll = () => {
    if (editorNode != null) {
        if (props.keepOffset && !isEmpty(props.offsetKey)) {
            editorNode.setScrollPosition({ scrollTop: scrollOffset.top, scrollLeft: scrollOffset.left })
        } else {
            // reset offset if not needed
            editorNode.setScrollPosition({ scrollTop: 0, scrollLeft: 0 })
        }
    }
}

const destroyEditor = () => {
    if (editorNode != null && editorNode.dispose != null) {
        const model = editorNode.getModel()
        if (model != null) {
            model.dispose()
        }
        editorNode.dispose()
        editorNode = null
    }
}

const readonlyValue = computed(() => {
    return props.readonly || props.loading
})

const pref = usePreferencesStore()
onMounted(async () => {
    if (editorRef.value != null) {
        const { fontSize, fontFamily = ['monaco'] } = pref.editorFont
        editorNode = monaco.editor.create(editorRef.value, {
            // value: props.content,
            theme: pref.isDark ? 'rdm-dark' : 'rdm-light',
            language: props.language,
            lineNumbers: pref.showLineNum ? 'on' : 'off',
            links: pref.editorLinks,
            readOnly: readonlyValue.value,
            colorDecorators: true,
            accessibilitySupport: 'off',
            wordWrap: 'on',
            tabSize: 2,
            folding: pref.showFolding,
            dragAndDrop: pref.dropText,
            fontFamily,
            fontSize,
            scrollBeyondLastLine: false,
            automaticLayout: true,
            scrollbar: {
                useShadows: false,
                verticalScrollbarSize: '10px',
            },
            // formatOnType: true,
            contextmenu: false,
            lineNumbersMinChars: 2,
            lineDecorationsWidth: 0,
            minimap: {
                enabled: false,
            },
            selectionHighlight: false,
            renderLineHighlight: 'gutter',
        })

        // add shortcut for save
        editorNode.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, (event) => {
            emit('save')
        })

        editorNode.onDidScrollChange((event) => {
            // save scroll offset when changes, ie. content changes
            if (props.keepOffset && !event.scrollHeightChanged) {
                scrollOffset.top = event.scrollTop
                scrollOffset.left = event.scrollLeft
            }
        })

        editorNode.onDidLayoutChange((event) => {
            updateScroll()
        })

        // editorNode.onDidChangeModelLanguageConfiguration(() => {
        //     editorNode?.getAction('editor.action.formatDocument')?.run()
        // })

        if (editorNode.onDidChangeModelContent) {
            editorNode.onDidChangeModelContent(() => {
                emit('input', editorNode.getValue())
            })
        }
    }
})

watch(
    () => props.content,
    async (content) => {
        if (editorNode != null) {
            editorNode.setValue(content)
            await nextTick(() => emit('reset', content))
            updateScroll()
        }
    },
)

watch(
    () => props.resetKey,
    async () => {
        if (editorNode != null) {
            editorNode.setValue(props.content)
            await nextTick(() => emit('reset', props.content))
            updateScroll()
        }
    },
)

watch(
    () => props.offsetKey,
    () => {
        // reset scroll offset when key changed
        if (editorNode != null) {
            scrollOffset.top = 0
            scrollOffset.left = 0
            editorNode.setScrollPosition({ scrollTop: 0, scrollLeft: 0 })
        }
    },
)

watch(
    () => readonlyValue.value,
    (readOnly) => {
        if (editorNode != null) {
            editorNode.updateOptions({
                readOnly,
            })
        }
    },
)

watch(
    () => props.language,
    (language) => {
        if (editorNode != null) {
            const model = editorNode.getModel()
            if (model != null) {
                monaco.editor.setModelLanguage(model, language)
            }
        }
    },
)

watch(
    () => pref.isDark,
    (dark) => {
        if (editorNode != null) {
            editorNode.updateOptions({
                theme: dark ? 'rdm-dark' : 'rdm-light',
            })
        }
    },
)

watch(
    () => pref.editor,
    ({ showLineNum = true, showFolding = true, dropText = true, links = true }) => {
        if (editorNode != null) {
            const { fontSize, fontFamily } = pref.editorFont
            editorNode.updateOptions({
                fontSize,
                fontFamily,
                lineNumbers: showLineNum ? 'on' : 'off',
                folding: showFolding,
                dragAndDrop: dropText,
                links,
            })
        }
    },
    { deep: true },
)

onUnmounted(() => {
    destroyEditor()
})
</script>

<template>
    <div :class="{ 'editor-border': props.border === true }" style="position: relative">
        <div ref="editorRef" class="editor-inst" />
    </div>
</template>

<style lang="scss" scoped>
.editor-border {
    border: 1px solid v-bind('themeVars.borderColor');
    border-radius: v-bind('themeVars.borderRadius');
    padding: 3px;
    box-sizing: border-box;
}

.editor-inst {
    position: absolute;
    top: 2px;
    bottom: 2px;
    left: 2px;
    right: 2px;
}

:deep(.line-numbers) {
    white-space: nowrap;
}
</style>
